﻿#region Copyright(c) 2006 ZO, All right reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2006 ZO, All right reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1.  Redistribution of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//   2.  Redistribution in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//   3.  The name of the author may not be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -----------------------------------------------------------------------------
#endregion


#region Using directives

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using ZO.SmartCore.Core;
using ZO.SmartCore.IO;
using ZO.SmartCore.Reflection;
using AppDomainManager= ZO.SmartCore.Reflection.AppDomainManager;
using ZO.SmartCore.My4CSharp;

#endregion

namespace ZO.SmartCore.Plugins
{
    /// <summary>
    /// Represents an assembly which was dynamically loaded at runtime.
    /// </summary>
    public sealed class PluginAssembly : DisposableObject
    {

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="PluginAssembly"/> class.
        /// </summary>
        internal PluginAssembly(string assemblyPath,
            string friendlyName,
            Type pluginInterfaceType,
            bool shadowCopy,
            bool newAppDomain)
        {
            bool loaded;

            this.newAppDomain = newAppDomain;
            if (String.IsNullOrEmpty(friendlyName))
            {
                friendlyName = That.Computer.FileSystem.GetName(assemblyPath);
            } // if

            this._FriendlyName = friendlyName;

            //plugin will load in new app domain
            if (this.newAppDomain)
            {
                this.sandbox = new AppDomainLoader();


                //create domain
                this.sandbox.CreateDomain(assemblyPath, shadowCopy);


                //find all plugins
                loaded = this.DiscoverAndGetAllPluginsFromAppDomain(pluginInterfaceType);

            }
            else // plugin will load in current Domain
            {
                assemblySandBox = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(assemblyPath));

                AppDomainManager.Add(AppDomain.CurrentDomain, assemblySandBox);

                loaded = this.DiscoverAndGetAllPlugins(pluginInterfaceType);
                Console.WriteLine(assemblySandBox.Location);
            }

            if (loaded)
            {
                watcher = new FileWatcher(assemblyPath);

                watcher.FileChanged += new FileSystemEventHandler(OnFileChanged);

                watcher.FileDeleted += new FileSystemEventHandler(OnFileDelete);

                watcher.FileRenamed += new RenamedEventHandler(OnFileRenamed);


            }
        }
        #endregion

        #region Destructor

        #endregion

        #region Fields
        private FileWatcher watcher;
        private string _FriendlyName;
        private AppDomainLoader sandbox;
        private Assembly assemblySandBox;

        private bool newAppDomain;

        internal List<Plugin> pluginList = new List<Plugin>();

        #endregion

        #region Events
        /// <summary>
        /// Occurs when the loaded assembly file has been changed 
        /// </summary>
        public static event EventHandler<PluginAssemblyEventArgs> Changed;

        /// <summary>
        /// Occurs when the Plugin is loading
        /// </summary>
        public static event EventHandler<PluginEventArgs> Loading;


        /// <summary>
        /// Occurs when the Plugin is unloading
        /// </summary>
        public static event EventHandler<PluginEventArgs> Unloading;

        /// <summary>
        /// Occurs when the loaded assembly file has been Renamed
        /// </summary>
        public static event EventHandler<PluginAssemblyRenamedEventArgs> Renamed;

        /// <summary>
        /// Occurs when the loaded assembly file has been deleted 
        /// </summary>
        public static event EventHandler<PluginAssemblyEventArgs> Deleted;

        #endregion

        #region Operators

        #endregion

        #region Properties

        /// <summary>
        /// Returns the actual location of the loaded assembly. This value may be different than AssemblyFile if the assembly was ShadowCopied.
        /// </summary>
        public string AssemblyLocation
        {
            get
            {
                if (this.newAppDomain)
                {
                    return this.sandbox.Assemblies[0].AssemblyLocation;
                }
                return this.assemblySandBox.Location;
            }
        }

        /// <summary>
        /// Returns the full path of the original loaded assembly.
        /// </summary>
        public string AssemblyFile
        {
            get
            {
                if (this.newAppDomain)
                {
                    return this.sandbox.Assemblies[0].AssemblyFile;
                }
                return this.assemblySandBox.CodeBase;
            }
        }

        /// <summary>
        /// Gets/Sets the friendly name of the assembly. If not explictly set, this property will return the name of the assembly file without path or extension (ie: the assembly "C:\Plugins\Assembly.dll" will have a friendly name of "Assembly" )
        /// </summary>
        public string FriendlyName
        {
            get
            {
                return this._FriendlyName;
            }
        }

        /// <summary>
        /// Returns the collection of dynamically loaded objects (Plugins) discovered in the loaded assembly. 
        /// </summary>
        public ReadOnlyCollection<Plugin> Plugins
        {
            get
            {
                return new ReadOnlyCollection<Plugin>(this.pluginList);

            } // get
        } // Plugins


        #endregion

        #region Methods

        /// <summary>
        /// Called when [file renamed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.IO.RenamedEventArgs"/> instance containing the event data.</param>
        private void OnFileRenamed(object sender, RenamedEventArgs e)
        {
            if (Renamed != null)
            {
                Renamed(this, new PluginAssemblyRenamedEventArgs(this._FriendlyName, e));
            }
        }

        /// <summary>
        /// Called when [file delete].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.IO.FileSystemEventArgs"/> instance containing the event data.</param>
        private void OnFileDelete(object sender, FileSystemEventArgs e)
        {
            if (Deleted != null)
            {
                Deleted(this, new PluginAssemblyEventArgs(this._FriendlyName, e));
            }
        }

        /// <summary>
        /// Called when [file changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="T:System.IO.FileSystemEventArgs"/> instance containing the event data.</param>
        private void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            if (Changed != null)
            {
                Changed(this, new PluginAssemblyEventArgs(this._FriendlyName, e));
            }

        } // OnFileChanged

        /// <summary>
        /// Used to Detect All Plugins and add it to plugin List to current domain
        /// </summary>
        private bool DiscoverAndGetAllPlugins(Type pluginInterfaceType)
        {
            bool flag = false;
            Type value;
            foreach (Type type in assemblySandBox.GetTypes())
            {
                if ((type.IsPublic) && !(type.IsAbstract) && !(type.IsInterface))
                {
                    value = type.GetInterface(pluginInterfaceType.FullName, true);

                    if (value != null)
                    {
                        Plugin tempPlugin = new Plugin(type);

                        if(tempPlugin == null) continue;
                        
                        PluginEventArgs args = new PluginEventArgs(tempPlugin);
                        if (Loading != null)
                        {
                            Loading(this, args);
                        }

                        if (!args.Cancel)
                        {
                            tempPlugin.Assembly = this;
                            this.pluginList.Add(tempPlugin);
                            flag = true;
                        }
                    }
                }
            }
            return flag;

        } // FindPlugins


        /// <summary>
        /// Used to Detect All Plugins and add it to plugin List to another domain
        /// </summary>
        private bool DiscoverAndGetAllPluginsFromAppDomain(Type pluginInterfaceType)
        {
            bool flag = false;
            Type value;


			
            foreach (TypeLoader type in sandbox.Assemblies[0].Types)
            {

                if ((type.IsPublic) & !(type.IsAbstract) & !(type.IsInterface))
                {

                    value = type.GetInterface(pluginInterfaceType.FullName, true);



                    if (value != null)
                    {
                        Plugin tempPlugin = new Plugin(type);

                        if (tempPlugin == null) continue;


                        PluginEventArgs args = new PluginEventArgs(tempPlugin);
                        if (Loading != null)
                        {
                            Loading(this, args);
                        }

                        if (!args.Cancel)
                        {
                            tempPlugin.Assembly = this;




                            this.pluginList.Add(tempPlugin);
                            flag = true;
                        }
                    }
                }
            }

            return flag;

        } // FindPlugins

        /// <summary>
        /// Unloads the objects stored in the Plugins collection. For each object that implements the IDisposable interface, the IDisposable.Dispose() method will be called on that object. If the assembly was loaded into a new AppDomain, the AppDomain will be unloaded. 
        /// </summary>
        public void Unload()
        {
            this.Dispose();
        } // Unload

        /// <summary>
        /// Unloads the specified plugin.
        /// </summary>
        /// <param name="plugin">The plugin.</param>
        public void UnloadPlugin(Plugin plugin)
        {
            if (plugin == null)
            {
                return;
            }

            if (this.pluginList.Contains(plugin))
            {
                PluginEventArgs args = new PluginEventArgs(plugin);
                if (Unloading != null)
                {
                    Unloading(this, args);
                }

                if (!args.Cancel)
                {
                    plugin.Dispose();
                    this.pluginList.Remove(plugin);
                }
            }
        } // UnloadPlugin

        /// <summary>
        /// Unloads the specified plugin.
        /// </summary>
        /// <param name="pluginName">Name of the plugin.</param>
        public void UnloadPlugin(string pluginName)
        {
            if (String.IsNullOrEmpty(pluginName))
            {
                return;
            }


            foreach (Plugin var in this.pluginList)
            {
                if (var.Name == pluginName)
                {
                    PluginEventArgs args = new PluginEventArgs(var);
                    if (Unloading != null)
                    {
                        Unloading(this, args);
                    }

                    if (!args.Cancel)
                    {
                        var.Dispose();
                        this.pluginList.Remove(var);
                    }
                    break;
                }
            }

        } // UnloadPlugin

        /// <summary>
        /// Override This Method To Dispose Unmanaged Resources.
        /// </summary>
        protected override void DisposeManagedResources()
        {
            this.pluginList.Clear();

            if (sandbox != null)
            {
                sandbox.Dispose();
                this.sandbox = null;
            }
            if (!newAppDomain)
            {
                AppDomainManager.Remove(AppDomain.CurrentDomain, assemblySandBox);
            }
            this.assemblySandBox = null;

        }

        #endregion
    }
}
